﻿using System;
using System.IO;
using System.Runtime.CompilerServices;
using UtilZ.Dotnet.Ex.Base;
using UtilZ.Dotnet.Ex.Model;

namespace UtilZ.Dotnet.Ex.Audio
{
    unsafe public partial class WaveHelper
    {
        private const float _IeeeFloat_MIN = -0.999999f;
        private const float _IeeeFloat_MAX = 0.999999f;


        #region pcm转IeeeFloat


        /// <summary>
        /// pcm数据文件转换为IeeeFloat数据文件
        /// </summary>
        /// <param name="pcmFilePath">带头的pcm数据文件路径</param>
        /// <param name="pcm32FilePath">IeeeFloat数据文件存储路径</param>
        public static void PcmToIeeeFloat(string pcmFilePath, string pcm32FilePath)
        {
            if (string.IsNullOrEmpty(pcmFilePath))
            {
                throw new ArgumentNullException(nameof(pcmFilePath));
            }

            if (!File.Exists(pcmFilePath))
            {
                throw new FileNotFoundException("带头的pcm数据文件不存在", pcmFilePath);
            }

            if (string.IsNullOrEmpty(pcm32FilePath))
            {
                throw new ArgumentNullException(nameof(pcm32FilePath));
            }

            using (var disposabler = new Disposabler())
            {
                var srcFileStream = new FileStream(pcmFilePath, FileMode.Open, FileAccess.Read);
                disposabler.Add(srcFileStream, 0);

                var waveInfo = GetWavHead(srcFileStream);
                if (waveInfo == null || waveInfo.Encoding != WaveEncoding.Pcm)
                {
                    throw new InvalidOperationException($"原始文件{pcmFilePath}非{WaveEncoding.Pcm.ToString()}编码格式,转换失败.");
                }
                if (waveInfo.SampleBits != 16)
                {
                    throw new InvalidOperationException($"仅支持16位采样位数的pcm数据文件换为IeeeFloat数据文件转,当前数据采样位数为{waveInfo.SampleBits}");
                }

                int srcFileWaveHeadSize = GetWaveHeadSize(WaveEncoding.Pcm);
                var srcDataPtr = new UnmanagedMemoryPointer<short>((srcFileStream.Length - srcFileWaveHeadSize) / sizeof(short));
                disposabler.Add(srcDataPtr, 0);

                srcFileStream.Position = srcFileWaveHeadSize;
                srcFileStream.Read(srcDataPtr.Cast<byte>().ToSpan());

                var destDataPtr = new UnmanagedMemoryPointer<float>(srcDataPtr.Length);
                disposabler.Add(destDataPtr, 0);
                PrimitivePcmToIeeeFloat(srcDataPtr.DataPtr, destDataPtr.DataPtr, destDataPtr.Length);

                DirectoryInfoEx.CheckFilePathDirectory(pcm32FilePath);
                var destFileStream = new FileStream(pcm32FilePath, FileMode.CreateNew, FileAccess.Write);
                disposabler.Add(destFileStream, 0);

                destFileStream.Position = GetWaveHeadSize(WaveEncoding.IeeeFloat);
                destFileStream.Write(destDataPtr.Cast<byte>().ToSpan());

                FillPcmWavHead(destFileStream, WaveEncoding.IeeeFloat, DataSampleBits.BIT32, waveInfo.SampleRate, waveInfo.ChannelCount, (uint)destFileStream.Length);
            }
        }

        /// <summary>
        /// pcm数据转换为IeeeFloat数据
        /// </summary>
        /// <param name="pcmData">pcm数据</param>
        /// <returns>IeeeFloat数据</returns>
        public static float[] PcmToIeeeFloat(short[] pcmData)
        {
            if (pcmData == null)
            {
                throw new ArgumentNullException(nameof(pcmData));
            }

            float[] pcm32Data = new float[pcmData.Length];
            fixed (float* pcm32DataPtr = pcm32Data)
            {
                fixed (short* pcmDataPtr = pcmData)
                {
                    PrimitivePcmToIeeeFloat(pcmDataPtr, pcm32DataPtr, pcm32Data.Length);
                }
            }
            return pcm32Data;
        }

        /// <summary>
        /// pcm数据转换为IeeeFloat数据
        /// </summary>
        /// <param name="pcmData">pcm数据</param>
        /// <param name="pcmDataIndex">pcm数据起始索引</param>
        /// <param name="pcm32Data">IeeeFloat数据</param>
        /// <param name="pcm32DataIndex">IeeeFloat数据起始索引</param>
        /// <param name="length">转换数据长度</param>
        public static void PcmToIeeeFloat(short[] pcmData, int pcmDataIndex, float[] pcm32Data, int pcm32DataIndex, int length)
        {
            if (pcmData == null)
            {
                throw new ArgumentNullException(nameof(pcmData));
            }

            if (pcm32Data == null)
            {
                throw new ArgumentNullException(nameof(pcm32Data));
            }

            if (pcmDataIndex < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(pcmDataIndex), "pcm数据起始索引不能小于0");
            }

            if (pcm32DataIndex < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(pcm32DataIndex), "IeeeFloat数据不能小于0");
            }

            if (length < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(length), "转换数据长度不能小于0");
            }

            if (pcm32DataIndex + length > pcm32Data.Length)
            {
                throw new ArgumentOutOfRangeException(nameof(length), "\"IeeeFloat数据起始索引\"加\"转换数据长度\"大于了\"IeeeFloat数据\"长度");
            }

            if (pcmDataIndex + length > pcmData.Length)
            {
                throw new ArgumentOutOfRangeException(nameof(length), "\"pcm数据起始索引\"加\"转换数据长度\"大于了\"pcm数据\"长度");
            }

            fixed (float* pcm32DataPtr = pcm32Data)
            {
                fixed (short* pcmDataPtr = pcmData)
                {
                    PrimitivePcmToIeeeFloat(pcmDataPtr + pcmDataIndex, pcm32DataPtr + pcm32DataIndex, length);
                }
            }
        }

        /// <summary>
        /// pcm数据转换为IeeeFloat数据
        /// </summary>
        /// <param name="pcmData">pcm数据</param>
        /// <param name="pcm32Data">IeeeFloat数据</param>
        public static void PcmToIeeeFloat(Span<short> pcmData, Span<float> pcm32Data)
        {
#if NET4_0
            short* pcmDataPtr = pcmData.DataPtr;
            float* pcm32DataPtr = pcm32Data.DataPtr;
            
#else
            short* pcmDataPtr = (short*)Unsafe.AsPointer<short>(ref pcmData[0]);
            float* pcm32DataPtr = (float*)Unsafe.AsPointer<float>(ref pcm32Data[0]);
#endif
            PrimitivePcmToIeeeFloat(pcmDataPtr, pcm32DataPtr, Math.Min(pcmData.Length, pcm32Data.Length));
        }

        /// <summary>
        /// pcm数据转换为IeeeFloat数据
        /// </summary>
        /// <param name="pcmData">pcm数据</param>
        /// <param name="pcm32Data">IeeeFloat数据</param>
        public static void PcmToIeeeFloat(SpanZ<short> pcmData, SpanZ<float> pcm32Data)
        {
            PrimitivePcmToIeeeFloat(pcmData.DataPtr, pcm32Data.DataPtr, Math.Min(pcmData.Length, pcm32Data.Length));
        }

        private static void PrimitivePcmToIeeeFloat(short* pcmDataPtr, float* pcm32DataPtr, int length)
        {
            for (int i = 0; i < length; i++)
            {
                if (pcmDataPtr[i] < -32767)
                {
                    pcm32DataPtr[i] = _IeeeFloat_MIN;
                }
                else
                {
                    pcm32DataPtr[i] = (float)pcmDataPtr[i] / 32767.0f;
                }
            }
        }

        #endregion

        

        #region IeeeFloat转pcm

        /// <summary>
        /// pcm32数据文件转换为pcm数据文件
        /// </summary>
        /// <param name="pcm32FilePath">带头的pcm32数据文件路径</param>
        /// <param name="pcmFilePath">pcm数据文件存储路径</param>
        public static void IeeeFloatToPcm(string pcm32FilePath, string pcmFilePath)
        {
            if (string.IsNullOrEmpty(pcm32FilePath))
            {
                throw new ArgumentNullException(nameof(pcm32FilePath));
            }

            if (!File.Exists(pcm32FilePath))
            {
                throw new FileNotFoundException("带头的pcm32数据文件路径", pcm32FilePath);
            }

            if (string.IsNullOrEmpty(pcmFilePath))
            {
                throw new ArgumentNullException(nameof(pcmFilePath));
            }

            using (var disposabler = new Disposabler())
            {
                var srcFileStream = new FileStream(pcm32FilePath, FileMode.Open, FileAccess.Read);
                disposabler.Add(srcFileStream, 0);

                var waveInfo = GetWavHead(srcFileStream);
                if (waveInfo == null || waveInfo.Encoding != WaveEncoding.IeeeFloat)
                {
                    throw new InvalidOperationException($"原始文件{pcm32FilePath}非{WaveEncoding.IeeeFloat.ToString()}编码格式,转换失败.");
                }

                int srcFileWaveHeadSize = GetWaveHeadSize(WaveEncoding.IeeeFloat, waveInfo.HasFact);

                var srcDataPtr = new UnmanagedMemoryPointer<float>((srcFileStream.Length - srcFileWaveHeadSize) / sizeof(float));
                disposabler.Add(srcDataPtr, 0);
                srcFileStream.Read(srcDataPtr.Cast<byte>().ToSpan());

                var destDataPtr = new UnmanagedMemoryPointer<short>(srcDataPtr.Length);
                disposabler.Add(destDataPtr, 0);
                PrimitiveIeeeFloatToPcm(srcDataPtr.DataPtr, destDataPtr.DataPtr, srcDataPtr.Length);

                DirectoryInfoEx.CheckFilePathDirectory(pcmFilePath);
                var destFileStream = new FileStream(pcmFilePath, FileMode.CreateNew, FileAccess.Write);
                disposabler.Add(destFileStream, 0);

                destFileStream.Position = GetWaveHeadSize(WaveEncoding.Pcm);
                destFileStream.Write(destDataPtr.Cast<byte>().ToSpan());

                FillPcmWavHead(destFileStream, WaveEncoding.Pcm, DataSampleBits.BIT16, waveInfo.SampleRate, waveInfo.ChannelCount, (uint)(destDataPtr.Length * sizeof(short)));
            }
        }

        /// <summary>
        /// pcm32数据转换为pcm数据
        /// </summary>
        /// <param name="pcm32Data">pcm32数据</param>
        /// <returns>pcm数据</returns>
        public static short[] IeeeFloatToPcm(float[] pcm32Data)
        {
            if (pcm32Data == null)
            {
                throw new ArgumentNullException(nameof(pcm32Data));
            }

            short[] pcmData = new short[pcm32Data.Length * 8];

            fixed (float* pcm32DataPtr = pcm32Data)
            {
                fixed (short* pcmDataPtr = pcmData)
                {
                    PrimitiveIeeeFloatToPcm(pcm32DataPtr, pcmDataPtr, pcm32Data.Length);
                }
            }

            return pcmData;
        }

        /// <summary>
        /// pcm32数据转换为pcm数据
        /// </summary>
        /// <param name="pcm32Data">pcm32数据</param>
        /// <param name="pcm32DataIndex">pcm32数据起始索引</param>
        /// <param name="pcmData">pcm数据</param>
        /// <param name="pcmDataIndex">pcm数据起始索引</param>
        /// <param name="length">转换pcm32数据长度</param>
        public static void IeeeFloatToPcm(float[] pcm32Data, int pcm32DataIndex, short[] pcmData, int pcmDataIndex, int length)
        {
            if (pcm32Data == null)
            {
                throw new ArgumentNullException(nameof(pcm32Data));
            }

            if (pcmData == null)
            {
                throw new ArgumentNullException(nameof(pcmData));
            }

            if (pcm32DataIndex < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(pcm32DataIndex), "pcm32数据起始索引不能小于0");
            }

            if (pcmDataIndex < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(pcmDataIndex), "pcm数据起始索引不能小于0");
            }

            if (length < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(length), "转换数据长度不能小于0");
            }

            if (pcm32DataIndex + length > pcm32Data.Length)
            {
                throw new ArgumentOutOfRangeException(nameof(length), "\"pcm32数据起始索引\"加\"转换数据长度\"大于了\"pcm32数据\"长度");
            }

            if (pcmDataIndex + length * 8 > pcmData.Length)
            {
                throw new ArgumentOutOfRangeException(nameof(length), "\"pcm数据起始索引\"加\"转换数据长度\"大于了\"pcm数据\"长度");
            }

            fixed (float* pcm32DataPtr = pcm32Data)
            {
                fixed (short* pcmDataPtr = pcmData)
                {
                    PrimitiveIeeeFloatToPcm(pcm32DataPtr + pcm32DataIndex, pcmDataPtr + pcmDataIndex, length);
                }
            }

        }

        /// <summary>
        /// pcm32数据转换为pcm数据
        /// </summary>
        /// <param name="pcm32Data">pcm32数据</param>
        /// <param name="pcmData">pcm数据</param>
        public static void IeeeFloatToPcm(Span<float> pcm32Data, Span<short> pcmData)
        {
#if NET4_0
            short* pcmDataPtr = pcmData.DataPtr;
            float* pcm32DataPtr = pcm32Data.DataPtr;
#else
            short* pcmDataPtr = (short*)Unsafe.AsPointer<short>(ref pcmData[0]);
            float* pcm32DataPtr = (float*)Unsafe.AsPointer<float>(ref pcm32Data[0]);
#endif
            PrimitiveIeeeFloatToPcm(pcm32DataPtr, pcmDataPtr, Math.Min(pcm32Data.Length, pcmData.Length));
        }

        /// <summary>
        /// pcm32数据转换为pcm数据
        /// </summary>
        /// <param name="pcm32Data">pcm32数据</param>
        /// <param name="pcmData">pcm数据</param>
        public static void IeeeFloatToPcm(SpanZ<float> pcm32Data, SpanZ<short> pcmData)
        {
            PrimitiveIeeeFloatToPcm(pcm32Data.DataPtr, pcmData.DataPtr, Math.Min(pcm32Data.Length, pcmData.Length));
        }

        private static void PrimitiveIeeeFloatToPcm(float* pcm32DataPtr, short* pcmDataPtr, int length)
        {
            for (int i = 0; i < length; i++)
            {
                if (pcm32DataPtr[i] < _IeeeFloat_MIN)
                {
                    pcmDataPtr[i] = short.MinValue;
                }
                else if (pcm32DataPtr[i] > _IeeeFloat_MAX)
                {
                    pcmDataPtr[i] = short.MaxValue;
                }
                else
                {
                    pcmDataPtr[i] = (short)(pcm32DataPtr[i] * 32767.0f);
                }
            }
        }

        #endregion

    }

}
